package demo.swagger;


import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import demo.util.ArrayUtils;
import demo.util.enums.NameValueEnum;
import demo.validation.annotation.EnumValue;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import springfox.documentation.builders.ModelPropertyBuilder;
import springfox.documentation.service.AllowableListValues;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.ModelPropertyBuilderPlugin;
import springfox.documentation.spi.schema.contexts.ModelPropertyContext;
import springfox.documentation.spi.service.ExpandedParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterExpansionContext;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * swagger 枚举字段属性具体描述
 *
 * @author meilin.huang
 * @create 2020/11/13 20:41
 */
@Order(1)
@Primary
public class ApiModelEnumPropertyBuilder implements ModelPropertyBuilderPlugin, ExpandedParameterBuilderPlugin {

    @SuppressWarnings("all")
    @Override
    public void apply(ModelPropertyContext context) {
        if (context.getAnnotatedElement().isPresent()) {
            ApiModelProperty mp = AnnotationUtils.getAnnotation(context.getAnnotatedElement().get(), ApiModelProperty.class);
            if (mp != null) {
                return;
            }
        }
        BeanPropertyDefinition bpd = context.getBeanPropertyDefinition().orNull();
        if (bpd == null) {
            return;
        }
        ApiModelEnumProperty ev = bpd.getField().getAnnotation(ApiModelEnumProperty.class);
        if (ev == null) {
            return;
        }
        ModelPropertyBuilder builder = context.getBuilder();
        builder.description(ev.value());
        builder.required(ev.required());

        // 获取字段类型
        Class<?> rawType = bpd.getField().getRawType();
        // 是否为枚举类型，为枚举类型，对应的value需为枚举值的名称。而不是调用getValue方法获取值，因为枚举类型前端需传枚举值对象名才可以转换为枚举对象
        boolean isEnumType = Enum.class.isAssignableFrom(rawType);

        AllowableListValues values = getAllowableListValues(rawType, ev);
        // 如果字段类型不为枚举类型且不为字符串，则将枚举值信息增加至描述信息中
        if (!isEnumType && rawType != String.class) {
            builder.description(ev.value() + "\n[" + String.join(",\n", values.getValues()) + "]");
        } else {
            builder.allowableValues(values);
        }
    }

    @Override
    public void apply(ParameterExpansionContext context) {
        ApiModelEnumProperty apiModelEnumProperty = context.findAnnotation(ApiModelEnumProperty.class).orNull();
        if (apiModelEnumProperty == null) {
            return;
        }
        AllowableListValues allowableListValues = getAllowableListValues(context.getFieldType().getErasedType(), apiModelEnumProperty);
        context.getParameterBuilder().required(apiModelEnumProperty.required())
                .description(apiModelEnumProperty.value() + " => [" + String.join(", ", allowableListValues.getValues()) + "]");
    }

    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }

    /**
     * 获取允许的枚举值
     *
     * @param fieldType            字段类型
     * @param apiModelEnumProperty 注解信息
     * @return
     */
    @SuppressWarnings("all")
    private AllowableListValues getAllowableListValues(Class<?> fieldType, ApiModelEnumProperty apiModelEnumProperty) {
        // 是否为枚举类型，为枚举类型，对应的value需为枚举值的名称。而不是调用getValue方法获取值，因为枚举类型前端需传枚举值对象名才可以转换为枚举对象
        boolean isEnumType = Enum.class.isAssignableFrom(fieldType);

        // enumClass为默认值，则获取字段对应的class
        Class<?> enumClass = apiModelEnumProperty.enumClass();
        if (enumClass == EnumValue.DefaultEnum.class && isEnumType) {
            enumClass = fieldType;
        }
        NameValueEnum[] enumConstants = (NameValueEnum[]) enumClass.getEnumConstants();

        String[] vs = apiModelEnumProperty.values();
        // 如果values不为空，则为values中指定枚举值
        if (ArrayUtils.isEmpty(vs)) {
            return new AllowableListValues(Arrays.stream(enumConstants).map(x -> getPair(isEnumType ? x.toString() : String.valueOf(x.getValue()), x.getName()))
                    .collect(Collectors.toList()), enumClass.getName());
        }

        List<String> vl = new ArrayList<>();
        for (NameValueEnum ce : enumConstants) {
            String code = isEnumType ? ce.toString() : String.valueOf(ce.getValue());
            if (ArrayUtils.contains(vs, code)) {
                vl.add(getPair(code, ce.getName()));
            }
        }
        return new AllowableListValues(vl, enumClass.getName());
    }

    private String getPair(String code, String name) {
        return code + ":" + name;
    }
}
